package edu.mit.csail.relo.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.sesame.sail.StatementIterator;
import org.openrdf.vocabulary.RDF;

import edu.mit.csail.relo.ReloCore;
import edu.mit.csail.relo.ReloPlugin;
import edu.mit.csail.relo.store.ReloRdfRepository;
import edu.mit.csail.relo.store.StoreUtil;
import edu.mit.csail.relo.ui.ReloEditor;

/**
 * @author Elizabeth L. Murnane
 */

public class LoadUtils {
	
	static final Logger logger = ReloPlugin.getLogger(LoadUtils.class);
	
	/**
	 * If the repository does not contain any statement with the given 
	 * subject, method returns true 
	 *  
	 * @return true if the given IResource is outdated, false otherwise
	 *
	 */
	public static boolean isOutdated(ReloRdfRepository repo, Resource subj, IResource parentRDFFileResource) {
		return isOutdated(repo, subj, null, null, parentRDFFileResource);
	}
	
	/**
	 * If the repository does not contain any statement with the given 
	 * subject, predicate, and object, method returns true
	 *  
	 * @return true if the given IResource is outdated, false otherwise
	 *
	 */
	public static boolean isOutdated(ReloRdfRepository repo, Resource subj, URI pred, Value obj, IResource parentRDFFileResource) {
		
		//Return false if the given IResource does not exist
		if(!parentRDFFileResource.exists()) return false;
		//user added links
		if(ReloCore.namedRel.equals(pred)) return false;
		//Return false if the repository contains any statement with the given subj, pred, and obj
		StatementIterator it = repo.getStatements(subj, pred, obj);
		if (it.hasNext()) return false;
				
		return true;
	}

	 /**
	  * Creates a problem marker on the given out of date IResource. 
	  * An error message shows up in the error log, all file and editor icons are 
	  * flagged with an error decoration, and when the file is opened in a text 
	  * editor, the line containing the outdated statement is flagged
	  * 
	  */
	public static void addErrors(IResource parentRDFFileResource, Resource subj, URI pred) {
		try {
			//Remove any previous errors on the bad resource and add the current error
			parentRDFFileResource.deleteMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO);
			IMarker errorMarker = parentRDFFileResource.createMarker(IMarker.PROBLEM);
			errorMarker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
			
			//If problem is a link problem, it is given by predicate. Otherwise, given by subject
			String problem = "";
			if(pred==null) {
				problem = subj.toString().substring(subj.toString().lastIndexOf("$")+1);
			} else {
				problem = pred.toString().substring(pred.toString().lastIndexOf("#")+1);
			}
			errorMarker.setAttribute(IMarker.MESSAGE, "Diagram is outdated. " +problem+ " no longer exists");
			
			//Flag the line containing out-of-date statement when file is opened in text editor
			int lineNumber = 1;
			if(parentRDFFileResource instanceof IFile) {
				IFile badFile = (IFile)parentRDFFileResource;
				BufferedReader reader = new BufferedReader(new InputStreamReader(badFile.getContents(true)));
				String line = reader.readLine();
				while(line!=null && !line.contains(problem)) {
					lineNumber++;
					line=reader.readLine();
				}
			}
			errorMarker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
			errorMarker.setAttribute(IMarker.LOCATION, "line " + lineNumber);
			
		} catch (CoreException e) {
			logger.error("Could not create error marker", e);
		} catch (IOException e) {
			logger.error("Could not read contents of outdated saved diagram", e);
		}
	}
	
	/**
	 * Check all the given .relo files to see if they are in 
	 * sync with the given repository
	 */
	public static void checkDependentReloFiles(IResource resource, List<Resource> reloFiles, final ReloRdfRepository repo) {
		
		for(Resource reloRes : reloFiles) {
			IResource reloFile = ReloCore.resourceToEclipseResource(repo, reloRes, resource.getProject());
			if (reloFile == null) continue;

			boolean isOutdated = false;
			
			//Find all statements the .relo file contains
			StatementIterator fileIt = repo.getStatements(reloRes, ReloCore.contains, null);
			while(fileIt.hasNext()) {
				Resource editPart = (Resource) fileIt.next().getObject();
				URI type = (URI) repo.getStatement(editPart, repo.rdfType, null).getObject();
				if(ReloCore.node.equals(type)) {
					//Find the model Resource and see if it still exists in the repository
					Resource modelRes = (Resource) repo.getStatement(editPart, ReloCore.model, null).getObject();
					isOutdated = LoadUtils.isOutdated(repo, modelRes, reloFile) ? true : isOutdated;
					if(isOutdated) {
						addErrors(reloFile, modelRes, null);
					}
				} else if (ReloCore.link.equals(type)) {
					//Find the source and destination resources and check whether the link between them still exists
					URI linkRes =(URI) repo.getStatement(editPart, ReloCore.model, null).getObject(); 
					Resource srcNode = (Resource) repo.getStatement(editPart, StoreUtil.createMemURI(RDF.SUBJECT), null).getObject();
					Resource dstNode = (Resource) repo.getStatement(editPart, StoreUtil.createMemURI(RDF.OBJECT), null).getObject();
					Resource modelResSrc = (Resource) repo.getStatement(srcNode, ReloCore.model, null).getObject();
					Resource modelResDst = (Resource) repo.getStatement(dstNode, ReloCore.model, null).getObject();
					isOutdated = LoadUtils.isOutdated(repo, modelResSrc, linkRes, modelResDst, reloFile) ? true : isOutdated;
					if(isOutdated) {
						addErrors(reloFile, modelResSrc, linkRes);
					}
				}
			}
			
			//If the .relo file was found to not be outdated, remove any errors on it
			if(!isOutdated && reloFile.exists()) {
				removeErrors(reloFile);
			}
		}
	}
	
	/**
	 * Returns a list of all .relo files containing a statement that depends 
	 * on the given IResource
	 *
	 */
	public static List<Resource> getDependentReloFiles(Resource classKey, ReloRdfRepository repo) {
		
		List<Resource> reloFiles = new ArrayList<Resource>();
		List<Resource> containedResources = new ArrayList<Resource>();
		containedResources.add(classKey);
		
		//Find all resources the given resource contains
		StatementIterator it = repo.getStatements(classKey, ReloCore.contains, null);
		while(it.hasNext()) {
			Resource contained = (Resource) it.next().getObject();
			containedResources.add(contained);
		}
		
		//For each contained Resource, find all .relo files that depend on 
		//that Resource
		for(Resource contained : containedResources) {
			StatementIterator nodeIter = repo.getStatements(null, ReloCore.model, contained);
			while(nodeIter.hasNext()) {
				Resource node = nodeIter.next().getSubject();
				StatementIterator reloFileIt = repo.getStatements(null, ReloCore.contains, node);
				while(reloFileIt.hasNext()) {
					Resource reloFile = reloFileIt.next().getSubject();
					reloFiles.add(reloFile);
				}
			}
		}
		
		//Check to make sure they are all .relo files
		List<Resource> reloFilesCopy = new ArrayList<Resource>(reloFiles);
		for(Resource file : reloFilesCopy) {
			StatementIterator fileIt = repo.getStatements(file, repo.rdfType, ReloCore.reloFile);
			if(!fileIt.hasNext()) {
				reloFiles.remove(file);
			}
		}
		
		return reloFiles;
	}
	
	/**
	 * Remove all problem markers on the given IResource
	 * 
	 */
	public final static void removeErrors(IResource accurateFile) {
		try {
			accurateFile.deleteMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO);
		} catch (CoreException e) {
			logger.error("Could not delete error marker", e);
		}
	}
		
	private static List<ReloEditor> buildersListeners = new ArrayList<ReloEditor>();
	
	public static void addBuildersListener(ReloEditor editor) {
		if(!buildersListeners.contains(editor)) {
			buildersListeners.add(editor);
		}
	}
	
	public static void removeBuildersListener(ReloEditor editor) {
		buildersListeners.remove(editor);
	}
	
	/**
	 * Checks all the editors in the buildersListeners list in order to correctly
	 * decorate or un-decorate their title images and the edit parts in their diagrams
	 *
	 */
	public static void checkBuildersListeners() {
		for(final ReloEditor editor : buildersListeners) {
			editor.checkEditor();
		}
	}
	
}
